#include "MemoryDma1.h" // IWYU pragma: keep
#include "base/embedded/interrupt/interrupt.h"
#include "base/string/define.h"
#include <cstddef>
#include <functional>
#include <stdexcept>

namespace
{
	std::function<void()> _dma2_stream1_isr;

}

extern "C"
{
	void DMA2_Stream1_IRQHandler()
	{
		if (_dma2_stream1_isr != nullptr)
		{
			_dma2_stream1_isr();
		}
	}
}

void bsp::MemoryDma1::InitializeCallback()
{
	_handle_context._handle.XferCpltCallback = [](DMA_HandleTypeDef *handle)
	{
		handle_context *context = reinterpret_cast<handle_context *>(handle);
		context->_self->OnCompleteCallback();
	};

	_handle_context._handle.XferErrorCallback = [](DMA_HandleTypeDef *handle)
	{
		handle_context *context = reinterpret_cast<handle_context *>(handle);
		context->_self->OnCompleteCallback();
	};

	_handle_context._handle.XferAbortCallback = [](DMA_HandleTypeDef *handle)
	{
		handle_context *context = reinterpret_cast<handle_context *>(handle);
		context->_self->OnCompleteCallback();
	};
}

void bsp::MemoryDma1::InitializeInterrupt()
{
	base::interrupt::disable_interrupt(static_cast<uint32_t>(IRQn_Type::DMA2_Stream1_IRQn));

	_dma2_stream1_isr = [this]()
	{
		HAL_DMA_IRQHandler(&_handle_context._handle);
	};

	base::interrupt::enable_interrupt(static_cast<uint32_t>(IRQn_Type::DMA2_Stream1_IRQn), 10);
}

bsp::MemoryDma1::~MemoryDma1()
{
	base::interrupt::disable_interrupt(static_cast<uint32_t>(IRQn_Type::DMA2_Stream1_IRQn));
	_dma2_stream1_isr = nullptr;
}

void bsp::MemoryDma1::Initialize()
{
	Initialize(1);
}

void bsp::MemoryDma1::Initialize(size_t align)
{
	_align = align;
	__HAL_RCC_DMA2_CLK_ENABLE();

	_handle_context._handle.Instance = DMA2_Stream1;
	_handle_context._handle.Init.Channel = DMA_CHANNEL_0;
	_handle_context._handle.Init.Direction = DMA_MEMORY_TO_MEMORY;
	_handle_context._handle.Init.Mode = DMA_NORMAL;
	_handle_context._handle.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
	_handle_context._handle.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL;
	_handle_context._handle.Init.MemBurst = DMA_MBURST_SINGLE;
	_handle_context._handle.Init.PeriphBurst = DMA_PBURST_SINGLE;
	_handle_context._handle.Init.PeriphInc = DMA_PINC_ENABLE;
	_handle_context._handle.Init.MemInc = DMA_MINC_ENABLE;
	_handle_context._handle.Init.Priority = DMA_PRIORITY_MEDIUM;

	switch (align)
	{
	case 1:
		{
			_handle_context._handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
			_handle_context._handle.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
			break;
		}
	case 2:
		{
			_handle_context._handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
			_handle_context._handle.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
			break;
		}
	case 4:
		{
			_handle_context._handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
			_handle_context._handle.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
			break;
		}
	default:
		{
			throw std::invalid_argument{CODE_POS_STR + "非法对齐方式。"};
		}
	}

	HAL_StatusTypeDef result = HAL_DMA_Init(&_handle_context._handle);
	if (result != HAL_StatusTypeDef::HAL_OK)
	{
		throw std::runtime_error{CODE_POS_STR + "初始化 DMA 失败。"};
	}

	InitializeCallback();
	InitializeInterrupt();
}

void bsp::MemoryDma1::Copy(uint8_t const *begin, uint8_t const *end, uint8_t *dst)
{
	if (reinterpret_cast<size_t>(begin) % _align != 0)
	{
		throw std::invalid_argument{CODE_POS_STR + "begin 地址必须对齐。"};
	}

	if (reinterpret_cast<size_t>(dst) % _align != 0)
	{
		throw std::invalid_argument{CODE_POS_STR + "dst 地址必须对齐。"};
	}

	if ((end - begin) % _align != 0)
	{
		throw std::invalid_argument{CODE_POS_STR + "内存段大小必须对齐。"};
	}

	if (end - begin == 0)
	{
		return;
	}

	base::task::MutexGuard g{_lock};

	_is_error = false;
	_is_abort = false;

	HAL_StatusTypeDef result = HAL_DMA_Start_IT(&_handle_context._handle,
												reinterpret_cast<uint32_t>(begin),
												reinterpret_cast<uint32_t>(dst),
												static_cast<uint32_t>((end - begin) / _align));

	if (result != HAL_StatusTypeDef::HAL_OK)
	{
		throw std::runtime_error{CODE_POS_STR + "DMA 启动失败。"};
	}

	_complete_signal.Acquire();
	if (_is_error)
	{
		throw std::runtime_error{CODE_POS_STR + "DMA 传输发生错误。"};
	}

	if (_is_abort)
	{
		throw std::runtime_error{CODE_POS_STR + "DMA 传输被中止。"};
	}
}
